#include "types.hpp"
#include "utils.hpp"
#include <ipmid/api.hpp>
#include "selutility.hpp"

#include  "phytiumras.hpp"
#include <phosphor-logging/lg2.hpp>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/elog.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
#include <xyz/openbmc_project/Logging/SEL/error.hpp>
#include <phosphor-logging/log.hpp>
#include "fault_predict_algo.hpp"


#include <chrono>
#include <filesystem>
#include <fstream>
#include <string>
#include <time.h>
#include <iostream>

extern "C"
{
#include "libcper/cper-parse.h"
#include <json-c/json.h>
}

namespace ipmi
{
namespace phytiumras
{
namespace cperinfo
{
void registerInspurPhytiumRasFunctions() __attribute__((constructor));

 using namespace phosphor::logging;
using Argument = xyz::openbmc_project::Common::InvalidArgument;
using namespace sdbusplus::xyz::openbmc_project::Common::Error;

struct PhytiumRasInfoData RasInfoData;

static void printBuf(const std::string& title, const std::vector<uint8_t>& pBuf)
{
    std::cout << title << std::endl;

    for (size_t i = 0; i < pBuf.size(); i++)
    {
        if (i % 16 == 0)
            std::cout << std::endl;

        fprintf(stderr, "0x%02x ", pBuf[i]);
    }
    std::cout << std::endl;
    return;
}

uint16_t calCrc16(const uint8_t* buf, uint32_t len)
{
    uint16_t kpoly = 0x1021;
    uint16_t kLeftBit = 0x8000;
    uint16_t kExtraRounds = 2;

    uint16_t crc = 0xffff;

    if (buf == nullptr)
    {
        return 0;
    }

    for (int i = 0; i < len + kExtraRounds; ++i)
    {
        for(int j = 0; j < 8; ++j)
        {
            uint16_t xorFlag = (crc & kLeftBit) ? 1 : 0;
            crc = crc << 1;
            if(i < len && (buf[i] & (1 << (7-j))))
            {
                crc++;
            }
            if(xorFlag)
            {
                crc ^= kpoly;
            }
        }
    }

    return crc;
}


static uint8_t getIpmiSelSeverity(uint8_t severity)
{
    uint8_t ipmiSeverity = 0;
    switch (severity)
    {
        case RECOVERABLE:
            ipmiSeverity = 0x01;//GENERIC_07H_TRANSITION_OFFSET_TO_NON_CRITICAL_FROM_OK;
            break;
        case FATAL:
            ipmiSeverity = 0x07;//GENERIC_07H_TRANSITION_OFFSET_TO_NON_RECOVERABLE;
            break;
        case CORRECTED:
        case INFORMATIONAL:
            ipmiSeverity = 0x00;//GENERIC_07H_TRANSITION_OFFSET_TO_OK;
            break;
        default:
        	break;
    }
    return ipmiSeverity;
}

static std::string getRasInformationSeverity(uint8_t severity,
                                             bool onlyServerity = false)
{
    std::string sensorSeverity;
    switch (severity)
    {
        case RECOVERABLE:
            sensorSeverity = "Recoverable";
            break;
        case FATAL:
            sensorSeverity = "Fatal";
            break;
        case CORRECTED:
            sensorSeverity = "Corrected";
            break;
        case INFORMATIONAL:
            sensorSeverity = "Informational";
            break;
    }

    if (onlyServerity)
    {
        return sensorSeverity;
    }

    return std::string(sensorSeverity);
}

static std::string getUniqueEntryID(std::string& prefix)
{
    static time_t prevTs = 0;
    static int index = 0;

    // Get the entry timestamp
    std::time_t curTs = time(0);
    std::tm timeStruct = *std::localtime(&curTs);
    char buf[80];
    // If the timestamp isn't unique, increment the index
    if (curTs == prevTs)
    {
        index++;
    }
    else
    {
        // Otherwise, reset it
        index = 0;
    }
    // Save the timestamp
    prevTs = curTs;
    strftime(buf, sizeof(buf), "%Y-%m-%d.%X", &timeStruct);
    std::string uniqueId(buf);
    uniqueId = prefix + uniqueId;
    if (index > 0)
    {
        uniqueId += "_" + std::to_string(index);
    }
    return uniqueId;
}



static void addIpmiSelLog(std::string sensorPath,
                          std::vector<uint8_t>& eventData)
{
 
    if (sensorPath.empty())
    {
        return;
    }

   using namespace xyz::openbmc_project::Logging::SEL;
    using namespace phosphor::logging;
    using SELCreated = sdbusplus::xyz::openbmc_project::Logging::SEL::Error::Created;

    static constexpr auto recordType = 7;
    static constexpr auto generatorID = 0x20; // bmc
    auto selDataStr = ipmi::sel::toHexStr(eventData);

    report<SELCreated>(
        Created::RECORD_TYPE(recordType), Created::GENERATOR_ID(generatorID),
        Created::SENSOR_DATA(selDataStr.c_str()), Created::EVENT_DIR(true),
        Created::SENSOR_PATH(sensorPath.c_str()));


}

uint8_t getMemoryChannel(uint64_t& physicalAddress)
{  
    uint8_t channelarray[8] = {1,0,6,7,2,3,5,4};
    uint8_t die = 0;
    uint8_t maskdata = 0;
    uint8_t channel = 0;

    die = (physicalAddress  /  0x100000000000) % 4;

    maskdata = (physicalAddress >> 9) & 0x1;

    channel = channelarray[maskdata];
  
    return channel;
}

uint8_t getMemorySlot(uint64_t& physicalAddress)
{

    return 0;
}


uint8_t getMemorySocketid(uint64_t& physicalAddress)
{
    uint32_t die = 0;

    die = physicalAddress / 0x100000000000;

    die = die / 4;

    return die;
}

static std::string getSensorPath(uint8_t sensorType)
{
    std::string sensorPath;
    switch (sensorType)
    {
        case PROCESSOR:
            sensorPath = ProcessorInventoryPath;
            break;
        case MEMORY:
            sensorPath = MemoryInventoryPath;
            break;
        case PCIE:
            sensorPath = PCIeIventoryPath;
            break;
    }

    return sensorPath;
}

static int parseArmProcessorEvent(std::string& primaryLogId,
                                  json_object* section, uint8_t severity,
                                  std::string& cperTimestamp)
{
    // RAS statistical Args
    uint64_t ceCount = 0;
    uint64_t ueCount = 0;
    uint8_t processorNum = 0xFF;

    uint64_t mpidrEl1 = 0;
    if (json_object_get_boolean(json_object_object_get(
            json_object_object_get(section, "validationBits"), "mpidrValid")))
    {
        mpidrEl1 = static_cast<uint64_t>(
            json_object_get_int(json_object_object_get(section, "mpidrEl1")));
        processorNum = (mpidrEl1 >> 32) & 0xf;
        processorNum = processorNum  / 4;
    }

    int8_t errorInfoNum = static_cast<int8_t>(
        json_object_get_int(json_object_object_get(section, "errorInfoNum")));
    json_object* errorInfos = json_object_object_get(section, "errorInfo");


    for (int j = 0; j < errorInfoNum; j++)
    {
        // IPMI SEL Args
        int8_t errorProcessorType = -1;
        int8_t transactionType = -1, operation = -1;

        std::string processorErrorType;
        json_object* errorType = json_object_object_get(
            json_object_array_get_idx(errorInfos, j), "errorType");
        if (errorType != nullptr)
        {
            errorProcessorType = static_cast<int8_t>(json_object_get_int(
                json_object_object_get(errorType, "value")));
            processorErrorType = json_object_get_string(
                json_object_object_get(errorType, "name"));
        }

        json_object* errorInformation = json_object_object_get(
            json_object_array_get_idx(errorInfos, j), "errorInformation");
        if (errorInformation != nullptr)
        {
            json_object* validationBits =
                json_object_object_get(errorInformation, "validationBits");

            if (json_object_get_boolean(json_object_object_get(
                    validationBits, "transactionTypeValid")))
            {
                transactionType = static_cast<int8_t>(
                    json_object_get_int(json_object_object_get(
                        json_object_object_get(errorInformation,
                                               "transactionType"),
                        "value")));
            }

            if (json_object_get_boolean(
                    json_object_object_get(validationBits, "operationValid")))
            {
                operation = static_cast<int8_t>(
                    json_object_get_int(json_object_object_get(
                        json_object_object_get(errorInformation, "Operation"),
                        "value")));
            }
            
            if (json_object_get_boolean(
                    json_object_object_get(validationBits, "correctedValid")))
            {
                if (json_object_get_int(json_object_object_get(
                        json_object_object_get(errorInformation, "Corrected"),
                        "value")))
                    ceCount++;
                else
                    ueCount++;
            }
        }

        // Construct the ipmi sel field
        // and generate sel events
        std::vector<uint8_t> eventData(3, 0);
        uint8_t ipmiSeverity = getIpmiSelSeverity(severity);

        eventData[0] = 0xA0 | ipmiSeverity & 0x0f;

        if (errorProcessorType != -1)
        {
            eventData[1] |= (errorProcessorType & 0x3)
                            << 4; // Processor Error Type
        }

        if (mpidrEl1 != 0)
        {
            eventData[1] |= processorNum; // Processor ID
        }

        if (transactionType != -1)
        {
            eventData[2] |= (transactionType & 0x3) << 4;
        }

        if (operation != -1)
        {
            eventData[2] |= operation & 0xf;
        }

        addIpmiSelLog(ProcessorInventoryPath, eventData);
        DeviceCECount cpu_device(CPU,std::make_tuple(processorNum));
    }

    return 0;
}

static int parsePlatformMemoryEvent(std::string& primaryLogId,
                                    json_object* section, uint8_t severity,
                                    std::string& cperTimestamp)
{
    // IPMI SEL Args
    uint8_t errorChannel = 0xff, errorSlot = 0xff, errorsocketid = 0xff;

    int8_t memoryErrorType = -1;
    uint64_t physicalAddress = 0, moduleHandle = 0;

    if (json_object_get_boolean(json_object_object_get(
            json_object_object_get(section, "validationBits"),
            "memoryErrorTypeValid")))
    {
        memoryErrorType =
            static_cast<uint8_t>(json_object_get_int(json_object_object_get(
                json_object_object_get(section, "memoryErrorType"), "value")));
    }

    if (json_object_get_boolean(json_object_object_get(
            json_object_object_get(section, "validationBits"),
            "moduleHandleValid")))
    {
        moduleHandle = json_object_get_uint64(
            json_object_object_get(section, "moduleSmbiosHandle"));
    }

    if (json_object_get_boolean(json_object_object_get(
            json_object_object_get(section, "validationBits"),
            "physicalAddressValid")))
    {
        physicalAddress = json_object_get_uint64(
            json_object_object_get(section, "physicalAddress"));

        errorChannel = getMemoryChannel(physicalAddress);
        errorSlot = getMemorySlot(physicalAddress);
        errorsocketid = getMemorySocketid(physicalAddress);


        fprintf(stderr,
                "[%s][%d]:Error Memory Channel Number = %d,Slot = %d "
                ",PhysicalAddress = %lx,add CE or UE Count 1,\n",
                __FUNCTION__, __LINE__, errorChannel, errorSlot,
                physicalAddress);
    }

 
    // Generate data in IPMI SEL format
    std::vector<uint8_t> eventData(3, 0);
    uint8_t ipmiSeverity = getIpmiSelSeverity(severity);

    eventData[0] = 0xA0 | ipmiSeverity & 0x0f;

    if (memoryErrorType != -1)
    {
        eventData[1] |= (memoryErrorType & 0x0f) << 4; // Memory Error Type
    }

    // Socket ID requires algorithm resolution,
    // which is not implemented at present
    eventData[1] |= errorsocketid; // Socket ID,

    if (errorChannel != -1)
    {
        eventData[2] |= (errorChannel & 0x3) << 4; // Memory Error Channel
    }

    if (errorSlot != -1)
    {
        eventData[2] |= errorSlot & 0xf; // Memory Error Slot
    }

    addIpmiSelLog(MemoryInventoryPath, eventData);
    DeviceCECount mem_device(MEMORY,std::make_tuple(errorsocketid,errorChannel,errorSlot));

    return 0;
}

static int parsePCIeEvent(std::string& primaryLogId, json_object* section,
                          uint8_t severity, std::string& cperTimestamp)
{
    // IPMI SEL Args
    int8_t deviceNumber = -1, functionNumber = -1, busNumber = -1,
           slotNumber = -1;
    uint16_t segmentNumber = -1;

    int8_t portType = -1;
    uint16_t vendorID = 0;
    uint16_t deviceID = 0;

    if (json_object_get_boolean(json_object_object_get(
            json_object_object_get(section, "validationBits"),
            "portTypeValid")))
    {
        portType =
            static_cast<int8_t>(json_object_get_int(json_object_object_get(
                json_object_object_get(section, "portType"), "value")));
    }

    if (json_object_get_boolean(json_object_object_get(
            json_object_object_get(section, "validationBits"),
            "deviceIDValid")))
    {
        busNumber = static_cast<uint8_t>(json_object_get_int(
            json_object_object_get(json_object_object_get(section, "deviceID"),
                                   "primaryOrDeviceBusNumber")));
        deviceNumber =
            (static_cast<uint8_t>(json_object_get_int(json_object_object_get(
                json_object_object_get(section, "deviceNumber"),
                "deviceID")))) &
            0x1f;
        functionNumber =
            (static_cast<uint8_t>(json_object_get_int(json_object_object_get(
                json_object_object_get(section, "deviceID"),
                "functionNumber")))) &
            0x7;
        segmentNumber =
            (static_cast<uint16_t>(json_object_get_int(json_object_object_get(
                json_object_object_get(section, "deviceID"),
                "segmentNumber")))) &
            0x7;

        vendorID =
            static_cast<uint16_t>(json_object_get_int(json_object_object_get(
                json_object_object_get(section, "vendorID"), "deviceID")));
        deviceID =
            static_cast<uint16_t>(json_object_get_int(json_object_object_get(
                json_object_object_get(section, "deviceID"), "deviceID")));

        slotNumber =
            (static_cast<uint16_t>(json_object_get_int(json_object_object_get(
                json_object_object_get(section, "deviceID"), "slotNumber"))));
    }

    // Generate data in IPMI SEL format
    std::vector<uint8_t> eventData(3, 0);
    uint8_t ipmiSeverity = getIpmiSelSeverity(severity);

    eventData[0] = 0xA0 | ipmiSeverity & 0x0f;

    if(segmentNumber != -1)
    {
    	eventData[1] |= (segmentNumber & 0xf) << 4;//segment
    }
    
    if (busNumber != -1)
    {
        eventData[1] |= busNumber & 0xf; // PCI Bus number
    }

    if (deviceNumber != -1)
    {
        eventData[2] |= (deviceNumber & 0xf) << 4; // PCI Device number
    }

    if (functionNumber != -1)
    {
        eventData[2] |= functionNumber & 0xf; // PCI Function number
    }

    addIpmiSelLog(PCIeIventoryPath, eventData);
    DeviceCECount pcie_device(PCIE,std::make_tuple(segmentNumber,busNumber,deviceNumber,functionNumber));

    return 0;
}


static int parseCPEREventAndReportLogs(json_object* ir,
                                       std::string& primaryLogId)
{
    uint8_t severity = 0;
    // The Default BMC time is used
    std::string cperTimestamp = primaryLogId.substr(9);
    json_object* header = json_object_object_get(ir, "header");
    if (json_object_get_boolean(json_object_object_get(
            json_object_object_get(header, "validationBits"),
            "timestampValid")))
    {
        cperTimestamp =
            json_object_get_string(json_object_object_get(header, "timestamp"));
    }

    json_object* sectionDescriptors =
        json_object_object_get(ir, "sectionDescriptors");

    json_object* sections = json_object_object_get(ir, "sections");

    int sdNum = json_object_array_length(sectionDescriptors);

    for (int i = 0; i < sdNum; i++)
    {
        json_object* sd = json_object_array_get_idx(sectionDescriptors, i);
        json_object* section = json_object_array_get_idx(sections, i);

        std::string primaryLogIdIndex = primaryLogId + "@" + std::to_string(i);

        const char* sectionType = json_object_get_string(json_object_object_get(
            json_object_object_get(sd, "sectionType"), "type"));

        severity =
            static_cast<uint8_t>(json_object_get_int(json_object_object_get(
                json_object_object_get(sd, "severity"), "code")));

        if (strcmp(sectionType, "ARM") == 0)
        {
            parseArmProcessorEvent(primaryLogIdIndex, section, severity,
                                   cperTimestamp);
        }
        else if (strcmp(sectionType, "Platform Memory") == 0)
        {
            parsePlatformMemoryEvent(primaryLogIdIndex, section, severity,
                                     cperTimestamp);
        }
        else if (strcmp(sectionType, "PCIe") == 0)
        {
            parsePCIeEvent(primaryLogIdIndex, section, severity, cperTimestamp);
        }
        else // Other types of CPER events are reported
        {
          
        }
    }

    return 0;
}




static void decodeCperRecord()
{
    std::string prefix("RAS_CPER_");
    std::string primaryLogId = getUniqueEntryID(prefix);
    std::string faultLogFilePath = std::string(CPER_LOG_DIR) + primaryLogId;

    std::cerr << "copyfile cper.dump to " << faultLogFilePath.c_str()
              << std::endl;
    std::filesystem::copy_file(std::string(TEMP_FAULT_LOG_PATH) + "cper.dump",
                               faultLogFilePath.c_str());
    std::cout << "zwh2." << std::endl;
    std::string cperFile = std::string(CPER_LOG_DIR) + primaryLogId;

    // Get a handle for the log file.
    FILE* fpCper = fopen(cperFile.c_str(), "r");
    if (fpCper == nullptr)
    {
        std::cout << "Could not open provided CPER file " << cperFile.c_str()
                  << ", file handle returned null." << std::endl;
        std::filesystem::rename(cperFile, std::string(CPER_LOG_DIR) +"unOpenable_CPER_" +primaryLogId);
        return;
    }

    // Convert.
    json_object* ir;
   ir = cper_to_ir(fpCper);
    fclose(fpCper);
    if (ir == nullptr)
    {
        std::cout << "Invalid CPER file:" << cperFile.c_str() << std::endl;
        std::filesystem::rename(cperFile, std::string(CPER_LOG_DIR) +"Invalid_CPER_" + primaryLogId);
        return;
    }
    else
    {
        std::cout << "CPER file:" << cperFile.c_str() << std::endl;
        std::filesystem::rename(cperFile, std::string(CPER_LOG_DIR) +"Invalid_CPER_" + primaryLogId);
    }

    std::cout << "CPER json: " << json_object_to_json_string(ir) << std::endl;

    parseCPEREventAndReportLogs(ir, primaryLogId);

    json_object_put(ir);

    std::cerr << "Generate CPER events: " << primaryLogId << std::endl;
    return;
}

static int mergeCperFile()
{
    std::ifstream ifs;
    std::vector<uint8_t> tmpBuf;

    std::ofstream out(std::string(TEMP_FAULT_LOG_PATH) + "cper.dump",
                      std::ofstream::binary);
    if (!out.is_open())
    {
        std::cerr << "Can not open ofstream for CPER binary file\n";
        return -1;
    }

    for (int index = 0; index <= RAS_IPMI_MAX_PART_NUM; index++)
    {
        std::string cperTempFile(std::string(TEMP_FAULT_LOG_PATH) + "cper.tmp" +
                                 std::to_string(index));

        if (!std::filesystem::exists(cperTempFile))
        {
            index = RAS_IPMI_MAX_PART_NUM - 1;
            continue;
        }

        ifs.open(cperTempFile, std::ios_base::binary);
        if (!ifs.is_open())
        {
            std::cerr << "Can not open ifstream for CPER binary file\n";
            return -1;
        }
        ifs.seekg(0, std::ios::end);
        tmpBuf.resize(ifs.tellg());
        ifs.seekg(0);
        ifs.read(reinterpret_cast<char*>(tmpBuf.data()), tmpBuf.size());

        out.write(reinterpret_cast<const char*>(tmpBuf.data()), tmpBuf.size());

        ifs.close();
    }
    out.close();

    return 0;
}

static int createCperTempFile(const std::vector<uint8_t>& reqData)
{
    if (!std::filesystem::is_directory(TEMP_FAULT_LOG_PATH))
        std::filesystem::create_directories(TEMP_FAULT_LOG_PATH);

    std::string cperFile = std::string(TEMP_FAULT_LOG_PATH) + "cper.tmp" +
                           std::to_string(reqData[PART_ID_OFFSET]);
    std::ofstream out(cperFile.c_str(), std::ofstream::binary);
    if (!out.is_open())
    {
        std::cerr << "Can not open ofstream for CPER binary file" << std::endl;
        return -1;
    }

    const char* data = reinterpret_cast<const char*>(reqData.data());
    out.write(&data[1], reqData.size() - 1);
    out.close();

    return 0;
}

RspType<uint16_t> ipmiPhytiumRasInfoRecv(ipmi::Context::ptr ctx, const std::vector<uint8_t>& reqData)
{
    printBuf("CPER Data:", reqData);
   // std::cout << "CPER id: " << reqData[PART_ID_OFFSET] << std::endl;
    RasInfoData.CurrentPartId =reqData[PART_ID_OFFSET];
	RasInfoData.CurrentPartLen = reqData.size() - 1;

  if (reqData[PART_ID_OFFSET] == FIRST_CPER_PACKET)
    {
        RasInfoData.RecvTotalLen += RasInfoData.CurrentPartLen;

        if (std::filesystem::is_directory(TEMP_FAULT_LOG_PATH))
            std::filesystem::remove_all(TEMP_FAULT_LOG_PATH);
        int ret = createCperTempFile(reqData);
        if (ret < 0)
        {
            std::cerr << "create Cper Temp File Fail,Part ID = "
                      << reqData[PART_ID_OFFSET] << std::endl;
            return ipmi::responseUnspecifiedError();
        }
    }
    else if (reqData[PART_ID_OFFSET] == LAST_CPER_PACKET)
    {
        if(RasInfoData.CurrentPartLen != 0)
		{
			RasInfoData.RecvTotalLen += RasInfoData.CurrentPartLen;
		}

        if(RasInfoData.RecvTotalLen != 0)
		{
                if (!std::filesystem::is_directory(CPER_LOG_DIR))
                std::filesystem::create_directories(CPER_LOG_DIR);

                if (createCperTempFile(reqData) < 0)
                {
                    std::cerr << "create Cper Temp File Fail: part ID = "
                            << reqData[PART_ID_OFFSET] << std::endl;
                    return ipmi::responseUnspecifiedError();
                }

                if (mergeCperFile() < 0)
                {
                    std::cerr << "merge Cper File Fail" << std::endl;
                    return ipmi::responseUnspecifiedError();
                }

                decodeCperRecord();

                memset(&RasInfoData,0x00,sizeof(PhytiumRasInfoData));
		}
		else
		{
                return ipmi::responseUnspecifiedError();
		}

    }
    else if((RasInfoData.CurrentPartId - RasInfoData.RecvPartId) == 1)
    {
        RasInfoData.RecvPartId++;
		RasInfoData.RecvTotalLen += RasInfoData.CurrentPartLen;
        if (createCperTempFile(reqData) < 0)
        {
            std::cerr << "create Cper Temp File Fail: part ID = "
                      << reqData[PART_ID_OFFSET] << std::endl;
            return ipmi::responseUnspecifiedError();
        }
    }
    else//非法包
    {
        if(RasInfoData.CurrentPartId == 0)
		{
			memset(&RasInfoData,0x00,sizeof(PhytiumRasInfoData));
			RasInfoData.RecvTotalLen += RasInfoData.CurrentPartLen;
			 if (std::filesystem::is_directory(TEMP_FAULT_LOG_PATH))
             std::filesystem::remove_all(TEMP_FAULT_LOG_PATH);
            int ret = createCperTempFile(reqData);
            if (ret < 0)
            {
                std::cerr << "create Cper Temp File Fail,Part ID = "
                        << reqData[PART_ID_OFFSET] << std::endl;
                return ipmi::responseUnspecifiedError();
            }
		}
		else
		{
			memset(&RasInfoData,0x00,sizeof(PhytiumRasInfoData));
            return ipmi::responseUnspecifiedError();
		}
    }

    return ipmi::responseSuccess(calCrc16(reqData.data(), reqData.size()));

}


void registerInspurPhytiumRasFunctions()
{

    registerHandler(prioOemBase, netFnPhytiumRas, phytiumras::cmdRasInfoRecv,
                          ipmi::Privilege::Operator, ipmiPhytiumRasInfoRecv);

}

}//cperinfo
} // namespace inspur
} // namespace ipmi
